package com.mindata.blockchain.core.manager;

import com.mindata.blockchain.block.Block;
import com.mindata.blockchain.block.Instruction;
import com.mindata.blockchain.common.PermissionType;
import com.mindata.blockchain.common.ResultPack;
import com.mindata.blockchain.common.RetryUtils;
import com.mindata.blockchain.common.SystemConfig;
import com.mindata.blockchain.core.bean.Member;
import com.mindata.blockchain.core.bean.MemberData;
import com.mindata.blockchain.core.bean.Permission;
import com.mindata.blockchain.core.bean.PermissionData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.tio.core.Node;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * 对联盟节点权限处理
 * <p>
 * 对Permission信息的存储和使用
 *
 * @author wuweifeng wrote on 2018/4/10.
 */
@Service
@Slf4j
public class PermissionManager {

    @Resource
    private RestTemplate restTemplate;
    @Resource
    private SystemConfig systemConfig;

    private static final String MANAGER_SUCCESS_CODE = "0";

    private static final int retry = 30;

    /**
     * 初始化权限信息
     * 避免新联盟节点加入时，同步区块而权限未初始化导致同步异常
     */
    @PostConstruct
    public void initPermission() {
        getMemberDataScheduled();
        fetchPermission();
    }

    /**
     * 将权限信息常驻内存
     */
    private static final Map<String, List<Permission>> PERMISSION_MAP = new HashMap<>();
    /**
     * 联盟节点存储
     */
    private static List<Member> MEMBER_LIST = new ArrayList<>();


    /**
     * 校验节点是否合法
     *
     * @param clientNode
     * @return
     */
    public ResultPack checkNodeAuth(Node clientNode) {
        if (MEMBER_LIST.isEmpty()) {
            getMemberDataScheduled();
        }
        Optional<Member> member = MEMBER_LIST.stream()
                .filter(m -> m.getIp().equals(clientNode.getIp()))
                .findFirst();

        if (member.isPresent()) {
            return ResultPack.succeed();
        }
        return ResultPack.failed();
    }


    /**
     * 定时器获取联盟节点信息
     *
     * @return
     */
    public ResultPack<List<Member>> getMemberDataScheduled() {
        MemberData memberData = getMemberDataScheduled(retry);
        //合法的客户端
        if (memberData.getCode() == 0) {
            MEMBER_LIST.clear();
            MEMBER_LIST = memberData.getMembers();
            return ResultPack.succeed(memberData.getMembers());
        }
        return ResultPack.failed(memberData.getMessage());
    }


    /**
     * 获取联盟操作权限是否成功
     *
     * @return
     */
    public boolean fetchPermission() {
        PermissionData permissionData = getPermissionData(retry);
        //获取到权限
        if (MANAGER_SUCCESS_CODE.equals(permissionData.getCode())) {
            List<Permission> permissionList = permissionData.getPermissions();
            savePermissionList(permissionList);
            return true;
        } else {
            return false;
        }
    }

    /**
     * 校验block内的所有指令的权限是否合法
     *
     * @param block 区块
     * @return 合法
     */
    public boolean checkPermission(Block block) {
        List<Instruction> instructions = block.getBlockBody().getInstructions();
        return checkPermission(instructions);
    }

    public boolean checkPermission(List<Instruction> instructions) {
        for (Instruction instruction : instructions) {
            String publicKey = instruction.getPublicKey();
            String tableName = instruction.getTable();
            byte operation = instruction.getOperation();
            //TODO 这块要优化，循环次数太多，需要精简
            if (!checkOperation(publicKey, tableName, operation)) {
                return false;
            }
        }
        return true;
    }


    /**
     * 校验某用户对某表的某个操作是否有权限
     *
     * @param publicKey 公钥
     * @param tableName 表名
     * @param operation 操作
     * @return 有权限true
     */
    private boolean checkOperation(String publicKey, String tableName, byte operation) {
        if (PERMISSION_MAP.isEmpty()) {
            fetchPermission();
        }
        List<Permission> permissionList = PERMISSION_MAP.get(tableName);

        Set<Byte> userPermissionSet = new HashSet<>();
        for (Permission permission : permissionList) {
            //如果是不限用户的情况，取到该表的所有公开的权限
            if ("*".equals(permission.getPublicKey())) {
                userPermissionSet.add(permission.getPermissionType());
            } else {
                //找到该publicKey的所有权限
                if (publicKey.equals(permission.getPublicKey())) {
                    userPermissionSet.add(permission.getPermissionType());
                }
            }
        }

        //判断该用户的权限是否包含operation
        return userPermissionSet.contains(PermissionType.OWNER)
                || userPermissionSet.contains(PermissionType.ALL)
                || userPermissionSet.contains(operation);
    }


    /**
     * 保存权限信息，static常驻内存，按table划分到map里
     *
     * @param permissions permissions
     */
    private void savePermissionList(List<Permission> permissions) {
        PERMISSION_MAP.clear();
        for (Permission permission : permissions) {
            String key = permission.getTableName();
            if (!PERMISSION_MAP.containsKey(key)) {
                PERMISSION_MAP.put(key, new ArrayList<>());
            }
            PERMISSION_MAP.get(key).add(permission);
        }
        log.info("所有的权限信息：" + PERMISSION_MAP);
    }

    /**
     * 获取联盟信息
     *
     * @param retry
     * @return
     */
    private MemberData getMemberDataScheduled(int retry) {

        RetryUtils retryUtils = new RetryUtils<MemberData>();
        @SuppressWarnings("unchecked")
        ResultPack<MemberData> resultPack = retryUtils.execRetries(() -> {
            String reqUrl = String.format(systemConfig.getManagerUrl() + "/webservice/member?name=%s&appId=%s&ip=%s&port=%s", systemConfig.getName(), systemConfig.getAppId(), systemConfig.getIp(), systemConfig.getPort());
            MemberData memberData;
            try {
                memberData = restTemplate.getForEntity(reqUrl, MemberData.class).getBody();
            } catch (Exception e) {
                log.error("从麦达区块链管理端获取已登记的各服异常:{}", e.getMessage());
                memberData = null;
            }
            if (memberData == null) {
                return ResultPack.failed();
            }
            return ResultPack.succeed(memberData);
        }, retry);

        if (resultPack.isFailed()) {
            log.error("请先启动md_blockchain_manager服务，并配置appId等属性，只有合法联盟链成员才能启动该服务");
            System.exit(0);
        }
        return resultPack.value();
    }


    /**
     * 从麦达区块链管理端获取权限信息
     *
     * @param retry
     * @return
     */
    private PermissionData getPermissionData(int retry) {

        RetryUtils retryUtils = new RetryUtils<PermissionData>();
        @SuppressWarnings("unchecked")
        ResultPack<PermissionData> resultPack = retryUtils.execRetries(() -> {
            PermissionData permissionData;
            try {
                permissionData = restTemplate.getForEntity(systemConfig.getManagerUrl() + "/webservice/permission?name=" + systemConfig.getName(), PermissionData.class).getBody();
            } catch (Exception e) {
                log.error("从麦达区块链管理端获取权限信息异常:{}", e.getMessage());
                permissionData = null;
            }
            if (permissionData == null) {
                return ResultPack.failed();
            }
            return ResultPack.succeed(permissionData);
        }, retry);

        if (resultPack.isFailed()) {
            log.error("请先启动md_blockchain_manager服务，并配置appId等属性，只有合法联盟链成员才能启动该服务");
            System.exit(0);
        }
        return resultPack.value();
    }


    /**
     * 格式 127.0.0.1:5001:1,127.0.0.1:5002:2,127.0.0.1:5003:3,127.0.0.1:5004:4,127.0.0.1:5005:5
     *
     * @return
     */
    public String getRaftClusterPeers() {
        StringBuffer clusterPeers = new StringBuffer();
        MEMBER_LIST.forEach(e -> {
            clusterPeers.append(String.format("%s:%s:%s", e.getIp(), e.getRaftPort(), e.getServerId())).append(",");
        });
        if (clusterPeers.toString().endsWith(",")) {
            return clusterPeers.substring(0, clusterPeers.length() - 1);
        }
        return clusterPeers.toString();
    }

    /**
     * 127.0.0.1:5001,127.0.0.1:5002,127.0.0.1:5003,127.0.0.1:5004,127.0.0.1:5005
     *
     * @return
     */
    public String getRaftClusterEndPoints() {
        StringBuffer clusterPeers = new StringBuffer();
        MEMBER_LIST.forEach(e -> {
            clusterPeers.append(String.format("%s:%s", e.getIp(), e.getRaftPort())).append(",");
        });
        if (clusterPeers.toString().endsWith(",")) {
            return clusterPeers.substring(0, clusterPeers.length() - 1);
        }
        return clusterPeers.toString();
    }
}
